package controller

import (
	"blog2021/configs"
	"blog2021/model"
	"blog2021/tools/strutil"
	"blog2021/tools/util"
	"fmt"
	"math/rand"
	"net/http"
	"net/url"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
)

func Index(c *gin.Context) {
	page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
	if err != nil {
		page = 1
	}

	if page <= 0 {
		page = 1
	} else if page > 10000 {
		page = 10000
	}

	posts := model.GetPostsPage(page)
	pageNumber := model.GetPageNumber(page)
	hotPosts := model.GetHotPosts()
	tags := model.HtmlTags()
	content := gin.H{
		"PostList":    posts,
		"PageNumber":  pageNumber,
		"HotPosts":    hotPosts,
		"Tags":        tags,
		"FriendLinks": configs.Instance().FriendLinks,
		"Popularize":  configs.Instance().Popularize,
	}

	c.HTML(http.StatusOK, "index.html", gin.H{
		"content": content,
		"token":   CurrentToken(c),
	})
}

func Post(c *gin.Context) {
	id := c.Param("id")
	if len(id) == 0 {
		ResponseMessage(c, "ERROR", "id is empty!")
		return
	}

	post, err := model.IncrPostById(id)
	if err != nil {
		ResponseMessage(c, "ERROR", "The article is not found!")
		return
	}

	recommendPosts := model.GetRecommendPosts()
	prevPost, nextPost := model.GetPostPrevNext(post)
	tags := model.HtmlTags()
	user := CurrentUser(c)
	content := gin.H{
		"PostDetail":     post,
		"PrevPost":       prevPost,
		"NextPost":       nextPost,
		"RecommentPosts": recommendPosts,
		"Tags":           tags,
		"Gitalk":         configs.GitalkEncodingStr(id),
		"User":           user,
		"Popularize":     configs.Instance().Popularize,
	}

	c.HTML(http.StatusOK, "post.html", gin.H{
		"content": content,
		"token":   user.Token,
	})
}

func About(c *gin.Context) {
	otherTags := []util.PairString{}
	if len(CurrentToken(c)) > 0 {
		otherTags = append(otherTags, util.PairString{
			First:  "/logout",
			Second: "登出",
		})
	} else {
		otherTags = append(otherTags, util.PairString{
			First:  "/login",
			Second: "登录",
		})
	}
	content := gin.H{
		"Sonsor":    true,
		"Gitalk":    configs.GitalkEncodingStr("about"),
		"OtherTags": otherTags,
	}
	c.HTML(http.StatusOK, "about.html", gin.H{
		"content": content,
		"token":   CurrentToken(c),
	})
}

func AboutAddComment(c *gin.Context) {
	data := model.Comment{}
	data.Name = c.PostForm("name")
	// data.Content := c.PostForm("email")
	data.Content = c.PostForm("comment")
	data.PostId = c.PostForm("postid")
	captcha := c.PostForm("captcha")
	keyCaptcha := c.PostForm("keyCaptcha")
	if !CaptchaVerify(keyCaptcha, captcha, true) {
		ResponseMessage(c, "ERROR", "captcha verify failed")
		return
	}

	if err := model.InsertComment(&data); err != nil {
		ResponseMessage(c, "ERROR", err.Error())
		return
	}

	c.Redirect(http.StatusTemporaryRedirect, "/about")
}

func ArchiveDesc(c *gin.Context) {
	hotPosts := model.GetHotPosts()
	tags := model.HtmlTags()
	content := gin.H{
		"Archive":  model.GetArchive("desc"),
		"HotPosts": hotPosts,
		"Tags":     tags,
	}
	c.HTML(http.StatusOK, "archive.html", gin.H{
		"content": content,
		"token":   CurrentToken(c),
	})
}

func ArchiveAsc(c *gin.Context) {
	hotPosts := model.GetHotPosts()
	tags := model.HtmlTags()
	content := gin.H{
		"Archive":  model.GetArchive("asc"),
		"HotPosts": hotPosts,
		"Tags":     tags,
	}
	c.HTML(http.StatusOK, "archive.html", gin.H{
		"content": content,
		"token":   CurrentToken(c),
	})
}

func Tag(c *gin.Context) {
	name := c.Param("name")
	if len(name) == 0 {
		ResponseMessage(c, "ERROR", "Tag name is empty!")
		return
	}
	name2 := c.Param("name2")
	if len(name2) > 0 {
		name = name + "/" + name2
	}

	hotPosts := model.GetHotPosts()
	posts := model.GetPostByTag(name)
	tags := model.HtmlTags()
	content := gin.H{
		"Title":    fmt.Sprintf("TAG & %v", name),
		"Posts":    posts,
		"HotPosts": hotPosts,
		"Tags":     tags,
	}
	c.HTML(http.StatusOK, "tagpost.html", gin.H{
		"content": content,
		"token":   CurrentToken(c),
	})
}

func Search(c *gin.Context) {
	keyword := c.Query("s")
	if len(keyword) <= 2 {
		ResponseMessage(c, "ERROR", "Search keywords are too short!")
		return
	}

	token := CurrentToken(c)
	containsContent := len(token) > 0
	hotPosts := model.GetHotPosts()
	posts := model.SearchPost(keyword, containsContent)
	keywordEscape := url.QueryEscape(keyword)
	r := strutil.NewCaseInsensitiveReplacer(keywordEscape, "<span class=\"hightlight-keyword\">"+keywordEscape+"</span>")
	for i, _ := range posts {
		title := url.QueryEscape(posts[i].Title)
		if title, err := url.QueryUnescape(r.Replace(title)); err == nil {
			posts[i].Title = title
		}
	}
	content := gin.H{
		"Title":    fmt.Sprintf("SEARCH & %v", keyword),
		"Keyword":  keyword,
		"Posts":    posts,
		"HotPosts": hotPosts,
	}
	c.HTML(http.StatusOK, "tagpost.html", gin.H{
		"content": content,
		"token":   token,
	})
}

func UploadImage(c *gin.Context) {
	var resp RespData
	pass := c.Request.FormValue("pass")
	if pass != "ningto" {
		resp.ErrCode = http.StatusBadRequest
		resp.ErrMsg = "No Auth"
		c.JSON(resp.ErrCode, resp)
		return
	}

	header, err := c.FormFile("file")
	if err != nil {
		resp.ErrCode = http.StatusBadRequest
		resp.ErrMsg = err.Error()
		c.JSON(resp.ErrCode, resp)
		return
	}

	if header.Size > (5 * 1024 * 1024) {
		resp.ErrCode = http.StatusBadRequest
		resp.ErrMsg = fmt.Sprintf("Picture is too big, size:%v bytes", header.Size)
		c.JSON(resp.ErrCode, resp)
		return
	}

	newFilename := fmt.Sprintf("%s-%s", time.Now().Format("20060102"), header.Filename)
	imagePath := path.Join(configs.UploadDir(), newFilename)
	if !util.PathExist(imagePath) {
		if err := c.SaveUploadedFile(header, imagePath); err != nil {
			resp.ErrCode = http.StatusInternalServerError
			resp.ErrMsg = err.Error()
			c.JSON(resp.ErrCode, resp)
			return
		}
	}

	resp.ErrCode = 0
	resp.ErrMsg = ""
	resp.Content = "/upload/" + newFilename
	c.JSON(200, resp)
}

func NotFound(c *gin.Context) {
	c.HTML(http.StatusOK, "404.html", gin.H{})
}

func Edit(c *gin.Context) {
	id := c.Param("id")
	tags := model.HtmlTags()
	selectTags := model.AllTags()
	post := model.Post{}
	if len(id) > 0 {
		postData, err := model.GetPostById(id)
		if err != nil {
			ResponseMessage(c, "ERROR", err.Error())
			return
		}
		post = postData
	}

	content := gin.H{
		"Tags":       tags,
		"SelectTags": selectTags,
		"Post":       post,
	}
	c.HTML(http.StatusOK, "edit.html", gin.H{
		"content": content,
		"token":   CurrentToken(c),
	})
}

func DeletePost(c *gin.Context) {
	user := CurrentUser(c)
	if len(user.Id) == 0 {
		ResponseMessage(c, "ERROR", "token expired,please relogin!")
		return
	}

	id := c.Param("id")
	if len(id) == 0 {
		ResponseMessage(c, "ERROR", "id is empty!")
		return
	}

	nextId := model.NextPostId(id)
	if err := model.DeletePostById(id); err != nil {
		ResponseMessage(c, "ERROR", err.Error())
		return
	}

	if len(nextId) > 0 {
		c.Redirect(http.StatusMovedPermanently, "/post/"+nextId)
	} else {
		c.Redirect(http.StatusMovedPermanently, "/")
	}
}

func OnEdit(c *gin.Context) {
	user := CurrentUser(c)
	if len(user.Id) == 0 {
		ResponseMessage(c, "ERROR", "token expired,please relogin!")
		return
	}

	id := c.PostForm("_id")
	type_ := c.PostForm("type")
	tags := c.PostForm("tags")
	title := c.PostForm("title")
	content := c.PostForm("content")
	if len(id) > 0 {
		oldPost, err := model.GetPostById(id)
		if err != nil {
			ResponseMessage(c, "ERROR", "old post not found")
			return
		}
		oldPost.Title = title
		oldPost.Profile = PostContent2Profile(content)
		oldPost.Content = content
		oldPost.Tags = strings.Trim(tags, ";")
		oldPost.Type = GetType(type_)
		if err = model.UpdatePost(oldPost); err != nil {
			ResponseMessage(c, "ERROR", "update post failed, "+err.Error())
			return
		}
	} else {
		newId, err := util.Uuidv4()
		if err != nil {
			ResponseMessage(c, "ERROR", "generate id failed, "+err.Error())
			return
		}
		newPost := model.Post{
			Id:      newId,
			Author:  user.Id,
			Title:   title,
			Profile: PostContent2Profile(content),
			Content: content,
			Pv:      0,
			Tags:    strings.Trim(tags, ";"),
			Type:    GetType(type_),
		}
		if err = model.AddPost(newPost); err != nil {
			ResponseMessage(c, "ERROR", "add post failed, "+err.Error())
			return
		}
	}
	ResponseMessage(c, "SUCCESS", "您成功发表了一篇文章")
}

func Login(c *gin.Context) {
	c.HTML(http.StatusOK, "login.html", gin.H{
		"content": gin.H{},
		"token":   CurrentToken(c),
	})
}

func Logout(c *gin.Context) {
	session := sessions.Default(c)
	session.Clear()

	session.Options(sessions.Options{MaxAge: -1})
	session.Save()

	ResponseMessage(c, "SUCCESS", "Logout success")
}

func OnLogin(c *gin.Context) {
	username := c.PostForm("username")
	password := c.PostForm("password")
	captcha := c.PostForm("captcha")
	keyCaptcha := c.PostForm("keyCaptcha")
	if !CaptchaVerify(keyCaptcha, captcha, true) {
		ResponseMessage(c, "ERROR", "captcha verify failed")
		return
	}

	user, err := model.GetUser("ningto", username)
	if err != nil {
		ResponseMessage(c, "ERROR", err.Error())
		return
	}

	if util.Sha1(password) != user.Password {
		ResponseMessage(c, "ERROR", "username or password error!")
		return
	}

	rand.Seed(time.Now().UnixNano())
	token := strconv.Itoa(rand.Int())
	token = util.Sha1(token)
	session := sessions.Default(c)
	session.Options(sessions.Options{MaxAge: 3600})
	session.Set("token", token)
	if session.Save() == nil {
		if model.UpdateUserToken(user, token) != nil {
			ResponseMessage(c, "ERROR", "update user token error")
			return
		}
	} else {
		ResponseMessage(c, "ERROR", "session save error")
		return
	}

	ResponseMessage(c, "SUCCESS", "Login success")
}

func Tools(c *gin.Context) {
	type ToolData struct {
		ImageUrl    string
		DownloadUrl string
		SourceUrl   string
		Code        string
		Title       string
		Desc        string
	}

	tools := []ToolData{
		{
			ImageUrl:    "https://ningto.com/upload/20210410-ntscreenshot-175726.png",
			DownloadUrl: "https://pan.baidu.com/s/1Xpg62O2tzkuOflVNcfGzTQ",
			SourceUrl:   "https://gitee.com/tujiaw/Acc",
			Code:        "提取码（losx）",
			Title:       "Acc",
			Desc:        "快速找到你要打开的应用程序。不用为了找到某个程序的启动图标而烦恼，不用在任务栏摆一堆的快捷方式。",
		},
		{
			ImageUrl:    "https://ningto.com/upload/20210410-ntscreenshot-182317.png",
			DownloadUrl: "https://pan.baidu.com/s/1mfpscMtSq6GpS85MvlcI6Q",
			SourceUrl:   "https://gitee.com/tujiaw/MonitorClipboard",
			Code:        "提取码（kna4）",
			Title:       "MonitorClipboard",
			Desc:        "监视鼠标位置、键盘按键、剪切板，将信息显示在界面上，仅学习使用，源码编译需要安装WTL。",
		},
		{
			ImageUrl:    "https://ningto.com/upload/20210328-ntscreenshot-201122.png",
			DownloadUrl: "https://pan.baidu.com/share/init?surl=l5OW9GeuUF0r5TFaBWkWZg",
			SourceUrl:   "https://gitee.com/tujiaw/gowallpaper",
			Code:        "提取码（pcqh）",
			Title:       "gowallpaper",
			Desc:        "设置必应壁纸作为桌面壁纸，每日更新，大量精美必应历史壁纸供您选择。",
		},
		{
			ImageUrl:    "https://ningto.com/upload/20210328-ntscreenshot-201551.png",
			DownloadUrl: "https://pan.baidu.com/s/1NqBVnT8XDhkxxS5xC3mk9g",
			SourceUrl:   "https://gitee.com/tujiaw/ntscreenshot",
			Code:        "提取码（qdn7）",
			Title:       "ntscreenshot",
			Desc:        "Windows截图工具，支持贴图，常见标注，马赛克。c键复制当前颜色，键盘像素级移动选区。",
		},
		{
			ImageUrl:    "https://ningto.com/upload/20210329-ntscreenshot-131416.png",
			DownloadUrl: "https://pan.baidu.com/s/1Js1KDPynKa3bWHT7Fl9ZNQ",
			SourceUrl:   "https://gitee.com/tujiaw/md5util",
			Code:        "提取码（ntvu）",
			Title:       "md5util",
			Desc:        "高效MD5生成工具，支持文本、批量文件、目录下的所有文件",
		},
		{
			ImageUrl:    "https://ningto.com/upload/20210329-ntscreenshot-131708.png",
			DownloadUrl: "https://gitee.com/tujiaw/gonetdisk",
			SourceUrl:   "https://gitee.com/tujiaw/gonetdisk",
			Code:        "",
			Title:       "gonetdisk",
			Desc:        "个人网盘，将文件存储在服务器上方便管理，分享链接，支持基本的文件、目录操作，充分利用带宽。",
		},
	}
	content := gin.H{
		"List": tools,
	}
	c.HTML(http.StatusOK, "tools.html", gin.H{
		"content":          content,
		"HideRightSidebar": true,
	})
}

func Html2markdown(c *gin.Context) {
	c.HTML(http.StatusOK, "html2markdown.html", gin.H{
		"content":          gin.H{},
		"HideRightSidebar": true,
	})
}

func Gather(c *gin.Context) {
	c.HTML(http.StatusOK, "gather.html", gin.H{
		"content": gin.H{
			"List":       model.GatherC.List(),
			"Popularize": configs.Instance().Popularize,
		},
	})
}

func GatherReload(c *gin.Context) {
	model.GatherC.Init()
	c.Redirect(http.StatusTemporaryRedirect, "/gather")
}

func GatherName(c *gin.Context) {
	name := c.Param("name")
	name, err := url.QueryUnescape(name)
	if err != nil {
		c.JSON(http.StatusInternalServerError, err)
		return
	}
	items := model.GatherC.Get(name)
	c.JSON(http.StatusOK, items)
}
